#!/user/bin/env python
# -*- coding: utf-8 -*-
''' 
Author: Sjy
Description: 自定义布局组件

- 默认采用grid布局
- 根据传入的配置进行布局
- 本身不提供嵌套布局，想要嵌套布局则可以在布局的组件中自己处理
- 配置的数据结构如下
    styles:dict, 参数设置表
        gridColumn:int, 布局的列数，每个cell 组件所有flex加起来不能大于此值
        margin:list, 布局的边距 缺省为 [10, 10, 10, 10]
        spacing:int, 布局的间距 缺省为 10
    grid:list, 布局的网格，每个网格中包含一个或多个组件
        widget:QWidget, 组件
        styles:dict, 组件的样式参数
            "rowSpan": int, 组件的行跨度
            "colSpan": int, 组件的列跨度
'''

__all__ = ["CustomLayout"]

import uuid
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import (QWidget, QGridLayout, QVBoxLayout, QHBoxLayout)
from PyQt5.QtCore import Qt, pyqtSignal, QObject
from PyQt5.QtGui import QPainter, QColor, QPen


class CustomLayoutWidget(QWidget):
    """
    自定义布局组件
    基于信号槽机制的布局组件，支持动态布局和组件管理
    """
    
    def __init__(self, params: dict = {}, parent=None):
        """
        初始化布局组件
        
        Args:
            params (dict): 布局参数
            parent (QWidget): 父组件
        """
        super(CustomLayoutWidget, self).__init__(parent)
        
        # 存储组件
        self._widgets = {}
        
        
        # 初始化布局
        self._grid_used = set()  # 用于跟踪已使用的网格位置
        self._init_ui()
        
        # 设置参数
        self._render(params)
        
    def _init_ui(self):
        """初始化UI"""
        self.__main_layout = QGridLayout()
        self.setLayout(self.__main_layout)
        
    def _is_position_available(self, row: int, col: int, row_span: int, col_span: int, max_cols: int) -> bool:
        """
        检查指定位置是否可以放置组件
        
        Args:
            row (int): 起始行
            col (int): 起始列
            row_span (int): 行跨度
            col_span (int): 列跨度
            max_cols (int): 最大列数
            
        Returns:
            bool: 位置是否可用
        """
        # 检查是否超出网格边界
        if col + col_span > max_cols:
            return False
            
        # 检查是否与已使用的网格位置重叠
        for r in range(row, row + row_span):
            for c in range(col, col + col_span):
                if (r, c) in self._grid_used:
                    return False
        return True

    def _mark_grid_used(self, row: int, col: int, row_span: int, col_span: int):
        """
        标记网格位置为已使用
        
        Args:
            row (int): 起始行
            col (int): 起始列
            row_span (int): 行跨度
            col_span (int): 列跨度
        """
        for r in range(row, row + row_span):
            for c in range(col, col + col_span):
                self._grid_used.add((r, c))

    def _render(self, config: dict):
        """
        渲染布局
        
        Args:
            config (dict): 布局配置
        """
        if not config:
            return
            
        # 清空已使用的网格位置
        self._grid_used.clear()
            
        # 设置样式参数
        stylesParams = config.get('styles', {})
        self._apply_base_styles(stylesParams)
        
        # 渲染行
        grid = config.get('grid', [])
        max_cols = stylesParams.get('gridColumn', 10)  # 获取最大列数

        current_row = 0
        for row in grid:
            current_col = 0
            for item in row:
                # 获取组件的跨度信息
                item_params = item.get('styles', {})
                row_span = item_params.get('rowSpan', 1)
                col_span = item_params.get('colSpan', 1)
                
                # 查找可用位置
                found_position = False
                while current_col < max_cols:
                    if self._is_position_available(current_row, current_col, row_span, col_span, max_cols):
                        self._render_qwidget(item, current_row, current_col, row_span, col_span)
                        found_position = True
                        current_col += col_span
                        break
                    current_col += 1
                
                if not found_position:
                    # 如果当前行找不到位置，移动到下一行
                    current_row += 1
                    current_col = 0
                    if self._is_position_available(current_row, current_col, row_span, col_span, max_cols):
                        self._render_qwidget(item, current_row, current_col, row_span, col_span)
                        current_col = col_span
                    else:
                        # 如果下一行也找不到位置，继续向下移动
                        while not self._is_position_available(current_row, current_col, row_span, col_span, max_cols):
                            current_row += 1
                        self._render_qwidget(item, current_row, current_col, row_span, col_span)
                        current_col = col_span
            
            # 移动到下一行
            current_row += 1

    def _render_qwidget(self, item: dict, row: int, col: int, row_span: int, col_span: int):
        """
        渲染组件
        
        Args:   
            item (dict): 组件配置
            row (int): 起始行
            col (int): 起始列
            row_span (int): 行跨度
            col_span (int): 列跨度
        """
        widget = item.get('widget', None)
        layout = item.get('layout', None)

        if not (widget!=None or layout!=None):
            raise ValueError("widget or layout is required") 
        # 应用组件样式
        
            
        # 添加到布局
        if widget!=None:
            self._apply_widget_params(item)
            self.__main_layout.addWidget(widget, row, col, row_span, col_span)
        
        # 标记网格位置为已使用
        self._mark_grid_used(row, col, row_span, col_span)

    def _apply_base_styles(self, item: dict, layout=None):
        """
        应用样式参数
        
        Args:
            params (dict): 样式参数
            layout (QLayout, optional): 目标布局. Defaults to None.
        """
        if layout is None:
            layout = self.__main_layout
            
        # 设置边距
        margin = item.get('margin', [10, 10, 10, 10])
        layout.setContentsMargins(*margin)
        # 设置间距
        spacing = item.get('spacing', 10)
        layout.setSpacing(spacing)

        # 其他
    def _apply_widget_params(self, params: dict):
        """
        应用组件参数
        
        Args:
            widget (QWidget): 目标组件
            params (dict): 组件参数
                styles:dict, 组件样式参数
                widgetID:str, 组件ID
                widget:QWidget, 组件
        """
        widget = params.get('widget', None)
        if widget is None:
            raise ValueError("widget is required")
        if hasattr(widget, "_set"):
            widget._set(params)

        # 记录组件ID
        ID = params.get('ID', uuid.uuid4())
        if ID:
            self._widgets[ID] = widget


    def _update_qwidget(self, widget_id: str, params: dict):
        """
        更新组件
        
        Args:
            widget_id (str): 组件ID
            params (dict): 更新参数
        """
        widget = self._widgets.get(widget_id)
        if widget:
            self._apply_widget_params({**params, 'widget': widget})
    def _update(self, params: dict):
        """
        更新组件
        
        Args:
            params (dict): 更新整体
        """
        self._clear()
        self._render(params)

    def _clear(self):
        """
        清理布局信息，但保留组件对象
        用于重新布局时使用
        """
        # 清除所有布局
        while self.__main_layout.count():
            item = self.__main_layout.takeAt(0)
            # 不删除组件，只从布局中移除
            if item.widget():
                item.widget().setParent(None)
        
        # 清空已使用的网格位置
        self._grid_used.clear()
        # 清空布局字典，但保留组件字典
        self._widgets.clear()

    def get_widget(self, widget_id: str) -> QWidget:
        """
        获取组件
        
        Args:
            widget_id (str): 组件ID
            
        Returns:
            QWidget: 组件对象
        """
        return self._widgets.get(widget_id)


if __name__ == "__main__":
    from PyQt5 import QtWidgets,QtCore
    from PyQt5.QtWidgets import QApplication
    from PyQt5.QtGui import QPainter, QColor, QPen
    from PyQt5.QtCore import Qt
    app = QApplication([])
    class TestWidget(QWidget):
        """测试用的自定义组件，带边框和背景色"""
        def __init__(self, text, bg_color, parent=None):
            super(TestWidget, self).__init__(parent)
            self.text = text
            self.bg_color = bg_color
            self.setMinimumSize(100, 50)  # 设置最小尺寸
            
        def paintEvent(self, event):
            painter = QPainter(self)
            painter.setRenderHint(QPainter.Antialiasing)
            
            # 绘制背景
            painter.fillRect(self.rect(), QColor(self.bg_color))
            
            # 绘制边框
            pen = QPen(Qt.black, 2)
            painter.setPen(pen)
            painter.drawRect(self.rect())
            
            # 绘制文本
            painter.setPen(Qt.black)
            painter.drawText(self.rect(), Qt.AlignCenter, self.text)


    btnCancel = QtWidgets.QPushButton("关闭")
    btnCancel.setMaximumWidth(100)
    
    btnLayout = QtWidgets.QHBoxLayout()
    btnLayout.addWidget(btnCancel) 
    btnLayout.setAlignment(QtCore.Qt.AlignCenter)

    btnLayoutWidget = QtWidgets.QWidget()
    btnLayoutWidget.setLayout(btnLayout)


    
    config = {
        "styles": {
            "gridColumn": 12,
            "margin": [10, 10, 10, 10],
            "spacing": 10
        },
        "grid": [
            # 第一行：大标题区域
            [
                {
                    "styles": {
                        "rowSpan": 1,
                        "colSpan": 12,
                    },
                    "widget": TestWidget("标题区域 (1x12)", "#FFE4B5")  # 浅橙色
                }
            ],
            # 第二行：左侧导航 + 主要内容区
            [
                {
                    "styles": {
                        "rowSpan": 3,
                        "colSpan": 2,
                    },
                    "widget": TestWidget("导航区域 (3x2)", "#E6E6FA")  # 浅紫色
                },
                {
                    "styles": {
                        "rowSpan": 3,
                        "colSpan": 7,
                    },
                    "widget": TestWidget("主要内容区 (3x7)", "#F0FFF0")  # 浅绿色
                },
                {
                    "styles": {
                        "rowSpan": 3,
                        "colSpan": 3,
                    },
                    "widget": TestWidget("侧边栏 (3x3)", "#FFE4E1")  # 浅红色
                }
            ],
            # # 第三行：已经包含在第二行的合并中
            # [],
            # # 第四行：已经包含在第二行的合并中
            # [],
            # 第五行：功能按钮区
            [
                {
                    "styles": {
                        "rowSpan": 2,
                        "colSpan": 3,
                    },
                    "widget": TestWidget("按钮组1 (2x3)", "#B0E0E6")  # 浅蓝色
                },
                {
                    "styles": {
                        "rowSpan": 1,
                        "colSpan": 3,
                    },
                    "widget": TestWidget("按钮组2 (1x3)", "#FFB6C1")  # 浅粉色
                },
                {
                    "styles": {
                        "rowSpan": 1,
                        "colSpan": 3,
                    },
                    "widget": TestWidget("按钮组3 (1x3)", "#98FB98")  # 浅绿色
                },
                {
                    "styles": {
                        "rowSpan": 1,
                        "colSpan": 3,
                    },
                    "widget": TestWidget("按钮组4 (1x3)", "#DDA0DD")  # 浅紫色
                }
            ],
            # 第六行：底部信息区
            [
                {
                    "styles": {
                        "rowSpan": 1,
                        "colSpan": 3,
                    },
                    "widget": TestWidget("底部信息1 (1x3)", "#F0F8FF")  # 浅蓝色
                },
                {
                    "styles": {
                        "rowSpan": 1,
                        "colSpan": 6,
                    },
                    "widget": TestWidget("底部信息2 (1x6)", "#FFE4B5")  # 浅橙色
                }
            ],
            [ 
                { # 第二行第1个组件
                    "widget": btnLayoutWidget, # 自定义组件，要实现组件规范要求的方法、信号和槽函数
                    "styles":{
                        "colSpan": 12,
                    },
                },
            ]
        ]
    }


    dlg = QtWidgets.QDialog()
    dlg.setWindowTitle("自定义布局")
    dlg.setMinimumSize(1200, 800)

    layout = QtWidgets.QVBoxLayout()

    cusLayoutWidget= CustomLayoutWidget(config)
    layout.addWidget(cusLayoutWidget)
    

    btnClear = QtWidgets.QPushButton("清除")
    btnUpdate = QtWidgets.QPushButton("更新")
    btnClear.clicked.connect(cusLayoutWidget._clear)
    btnUpdate.clicked.connect(lambda *args, params=config: cusLayoutWidget._update(params))
    layout.addWidget(btnClear)
    layout.addWidget(btnUpdate)
    btnCancel.clicked.connect(dlg.close)
    dlg.setLayout(layout)
    dlg.show()

    # layout.setMinimumSize(1200, 800)  # 增加窗口尺寸以适应复杂布局
    # layout.show()
    app.exec_()

